﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EVEMap
{
    public class eveMap
    {
        #region members
        internal Dictionary<int, eveSolarSystem> m_Map;
        internal Dictionary<string, eveSolarSystem> m_MapByName;
        #endregion

        public eveMap()
        {
            createHelper();
        }


        /// <summary>
        /// Create a new map from an existing one, making a deep copy.
        /// </summary>
        /// <param name="other">the map from which to initialize this one</param>
        public eveMap(eveMap other)
        {
            createHelper();
            foreach (KeyValuePair<int, eveSolarSystem> elem in other.m_Map)
            {
                eveSolarSystem sys = new eveSolarSystem(elem.Value);
                m_Map.Add(elem.Key, sys);
                m_MapByName.Add(sys.Name, sys);
            }

        }

        private void createHelper()
        {
            m_Map = new Dictionary<int, eveSolarSystem>();
            m_MapByName = new Dictionary<string, eveSolarSystem>();
        }

        public void Add(int key, eveSolarSystem value)
        {
            m_Map.Add(key, value);
            m_MapByName.Add(value.Name.ToUpper(), value);
        }

        /// <summary>
        /// Find a the shortest list of system IDs between start & dest
        /// </summary>
        /// <param name="start">The system ID of the starting point</param>
        /// <param name="dest">The system ID of the destination</param>
        /// <returns>List of system IDs in the shortest route</returns>
        public List<int> Djikstra(int start, int dest)
        {
            List<int> result = new List<int>();
            Dictionary<int, eveSolarSystem> mapTmp = new Dictionary<int, eveSolarSystem>();
            int visitCount = 0;
            foreach (KeyValuePair<int, eveSolarSystem> sys in m_Map)
            {
                sys.Value.RouteDistance = int.MaxValue;
                mapTmp.Add(sys.Key, sys.Value);
            }
            //int currentSys = start;
            int currentDistance = 0;
            List<int> visitThisIter = new List<int>();
            visitThisIter.Add(start);
            bool found = false;
            while (mapTmp.Count > 0 && !found)
            {
                List<int> visitNextIter = new List<int>();
                foreach (int currentSys in visitThisIter)
                {
                    ++visitCount;
                    eveSolarSystem system = m_Map[currentSys];
                    system.RouteDistance = currentDistance;
                    if (system.ID == dest)
                    {
                        found = true;
                        break;
                    }
                    foreach (int jumpTmp in system.Jumps)
                    {
                        eveSolarSystem dummy;
                        if (mapTmp.TryGetValue(jumpTmp, out dummy))
                            // don't add jumpTmp if it's a node that was already visited
                            visitNextIter.Add(jumpTmp);
                    }
                    mapTmp.Remove(currentSys);
                }
                ++currentDistance;
                visitThisIter = visitNextIter;
                //visitThisIter.Clear();
                //visitThisIter.Concat(visitNextIter);
            }
            if (!found)
                return result;
            // now work backwards from dest to start to build up the result list
            visitThisIter.Clear();
            visitThisIter.Add(dest);
            result.Add(dest);
            eveSolarSystem returnSys = m_Map[dest];
            found = false;
            while (returnSys.ID != start)
            {
                // smallest route distance found for a system this iteration              
                int shortest = int.MaxValue;
                // id of system with shortest distance
                int nextSystem = 0;
                foreach (int nextSys in returnSys.Jumps)
                {
                    eveSolarSystem tmpSystem = m_Map[nextSys];
                    if (tmpSystem.RouteDistance < shortest)
                    {
                        shortest = tmpSystem.RouteDistance;
                        nextSystem = tmpSystem.ID;
                    }
                }
                returnSys = m_Map[nextSystem];
                result.Add(nextSystem);
            }
            result.Reverse();
            return result;
        }

        /// <summary>
        /// Return the list of system IDs that are within the given number of jumps of the given ID
        /// </summary>
        /// <param name="ID">The eveSolarSystem.ID from which to start</param>
        /// <param name="jumps">The number within which to look</param>
        /// <returns>List of IDs that are within jumps of starting ID</returns>
        public List<int> withinN(int ID, int jumps)
        {
            List<int> result = new List<int>();
            eveSolarSystem startSystem = m_Map[ID];
            foreach (int jump in startSystem.Jumps)
            {
                if (result.Contains(jump))
                    continue;
                result.Add(jump);
                if (jumps > 0)
                {
                    result = (List<int>)result.Concat(withinN(jump, jumps - 1));
                }
            }
            return result;
        }

        /// <summary>
        /// Attempt to move asset stacks closer to the hub by up to the given number of jumps
        /// todo: add a region parameter to restrict which assets are moved
        /// </summary>
        /// <param name="jumps">The maximum distance to move assets</param>
        public List<coalesceAssetsList> coalesceAssets(List<eveSolarSystem> regionSystemsWithAssets, int jumps = 3)
        {
            // put the most distant systems first
            regionSystemsWithAssets.Sort((x, y) => y.RouteDistance.CompareTo(x.RouteDistance));
            List<coalesceAssetsList> retval = new List<coalesceAssetsList>();
            foreach (eveSolarSystem tmpSystem in regionSystemsWithAssets)
            {
                if (tmpSystem.Security < 0.5)
                    continue;
                coalesceAssetsList tmp = tryCoalesceAssets(tmpSystem, jumps);
                if (tmp != null)
                    retval.Add(tmp);
            }
            return retval;
        }


        private coalesceAssetsList tryCoalesceAssets(eveSolarSystem sys, int jumps)
        {
            // skip hub
            if (sys.Route.Count == 1)
                return null;
            // is this system already pinned?
            if (sys.AssetsPinned)
                return null;
            // does sys have assets?
            coalesceAssetsList tmp = sys.BiggestStationAssets;
            double minAssetValue = Properties.Settings.Default.minCoalesceAssetValue;

            if (tmp != null)
            {
                // todo: make this tunable
                if (tmp.Collateral < minAssetValue)
                    return null;
                // start at subscript 1, since the route contains the starting system
                for (int i = 1; i < jumps; ++i)
                {
                    tmp.AddJump(sys.Route[i]);
                    eveSolarSystem sys2 = m_Map[sys.Route[i]];
                    // todo -- make this parameter/tunable
                    if (sys2.AssetsValue > minAssetValue)
                    {
                        // dock the assets here, done
                        eveStation dockStation = sys2.BiggestAssetsStation;
                        tmp.Dock(dockStation);
                        dockStation.AssetsPinned = true;
                        //tmp.coalesceEnd = dockStation.ID;
                        return tmp;
                    }
                }
            }
            return null;
        }

        public eveSolarSystem this[string key]
        {
            get
            {
                return m_MapByName[key];
            }
            set
            {
                m_MapByName[key] = value;
            }
        }

        public eveSolarSystem this[int key]
        {
            get
            {
                return m_Map[key];
            }
            set
            {
                m_Map[key] = value;
            }
        }

    }

}
